#include <stdint.h>
#include <stdio.h>
#include <limits.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <string.h>
#include <pthread.h>
#include "global.h"
#include "ietDoLib.h"
#include "prt_log.h"

#define LIB_VERSION(a,b,c)                  ((a << 24) + (b << 16) + (c))

#define DO_MAX_CHANNEL      32              /* DO 最大通道数 */
#if (HARD_VER == 0)
#define DO_DOM_CHANNEL      16              /* DO 在 DO 模块中通道数 */
#define DO_ACM_CHANNEL      12              /* DO 在 AC 模块中通道数 */
#elif (HARD_VER == 1)
#define DO_DOM_CHANNEL      24              /* DO 在 DO 模块中通道数 */
#define DO_ACM_CHANNEL      12              /* DO 在 AC 模块中通道数 */
#endif

#define DO_DRV_NAME         "pcie_do"       /* DO 驱动名称 */

struct pulse_t {
    uint16_t high;          /* 脉冲高电平时间, ms */
    uint16_t cycle;         /* 脉冲周期, ms */
    uint32_t num;           /* 脉冲个数 */
};

/* IOCTL 命令定义 */
#define IOCTL_DO_MAGIC                      'P'                                         /* 标识设备驱动 */
#define IOCTL_DO_SET_ALL_LEVEL              _IOW(IOCTL_DO_MAGIC, 1, uint32_t)           /* 配置 DO 所有通道电平 */
#define IOCTL_DO_GET_ALL_LEVEL              _IOR(IOCTL_DO_MAGIC, 2, uint32_t)           /* 获取 DO 所有通道电平 */
#define IOCTL_DO_SET_MODE                   _IOW(IOCTL_DO_MAGIC, 3, uint8_t)            /* 配置 DO 模式 */
#define IOCTL_DO_SET_TIMEOUT_STA            _IOW(IOCTL_DO_MAGIC, 4, uint8_t)            /* 配置 DO 吸合超时状态 */
#define IOCTL_DO_SET_TIMEOUT                _IOW(IOCTL_DO_MAGIC, 5, uint8_t)            /* 配置 DO 吸合超时时间 */
#define IOCTL_DO_SET_LEVEL                  _IOW(IOCTL_DO_MAGIC, 6, uint8_t)            /* 配置 DO 电平 */
#define IOCTL_DO_GET_LEVEL                  _IOW(IOCTL_DO_MAGIC, 7, uint8_t)            /* 获取 DO 电平 */
#define IOCTL_DO_SET_PULSE                  _IOW(IOCTL_DO_MAGIC, 8, struct pulse_t)     /* 配置 DO 脉冲参数 */
#define IOCTL_DO_START_PULSE                _IO(IOCTL_DO_MAGIC,  9)                     /* 开始 DO 脉冲 */
#define IOCTL_DO_STOP_PULSE                 _IO(IOCTL_DO_MAGIC,  10)                    /* 停止 DO 脉冲 */

#define IOCTL_DO_GET_DRV_VERSION            _IOR(IOCTL_DO_MAGIC, 101, uint32_t)         /* 获取驱动版本号 */
#define IOCTL_DO_GET_FPGA_VERSION           _IOR(IOCTL_DO_MAGIC, 102, uint32_t)         /* 获取 FPGA 版本号 */

/* 应用层 */
enum DoTimeoutSta {
    DO_TIMEOUT_OFF          = 0,        /* 关闭吸合超时 */
    DO_TIMEOUT_ON           = 1,        /* 打开吸合超时 */
};

/* 驱动层*/
enum DoDevMode {
    DO_DEV_MOD_LEVEL        = 0,        /* 电平输出 */
    DO_DEV_MOD_PULSE        = 1,        /* 脉冲输出 */
};

enum DoDevTimeoutSta {
    DO_DEV_TIMEOUT_OFF      = 0,        /* 关闭吸合超时 */
    DO_DEV_TIMEOUT_ON       = 1,        /* 打开吸合超时 */
};

struct ThreadCtrl_t {
    char name[64];                          /* 线程名 */
    pthread_t id;                           /* 线程标识 */
};

struct DoDev_t {
    void *parent;                           /* 父指针 */
    char name[32];                          /* 设备名 */
    uint8_t index;                          /* 设备号 */

    uint8_t setMode;                        /* 模式 */
    uint8_t timeoutSta;                     /* 吸合超时状态 */
    uint8_t timeout;                        /* 吸合超时时间 */
    uint8_t setLevel;                       /* 配置电平 */
    uint8_t getLevel;                       /* 获取电平 */
    struct pulse_t setPulse;                /* 脉冲 */

    int fd;                                 /* 文件 */
    char path[PATH_MAX];                    /* 路径 */
};

struct DoCtrl_t {
    char name[16];                          /* 控制器名 */
    uint8_t num;                            /* 设备数量 */
    uint8_t max_num;                        /* 最大数量 */
    struct DoDev_t dev[DO_MAX_CHANNEL];     /* 设备描述 */
    uint32_t drvVer;                        /* 驱动 版本号 */
    uint32_t fpgaVer;                       /* FPGA 版本号 */
    uint32_t libVer;                        /* 动态库 版本号 */

    uint8_t type;                           /* 模块类型 */
    uint32_t setAllLevel;                   /* 配置所有电平 */
    uint32_t getAllLevel;                   /* 获取所有电平 */
};
/* DO 控制器 */
static struct DoCtrl_t DoCtrl;

/* 获取设备数量 */
static uint8_t DOMGetDevNum(int type)
{
    uint8_t num = 0;

    switch (type) {
    case IET_ECU:
    case IET_DI:
        goto err_retrun;
    case IET_DO:
        num = DO_DOM_CHANNEL;
        break;
    case IET_AI:
    case IET_AO:
    case IET_PI:
    case IET_SOE:
        goto err_retrun;
    case IET_AC:
        num = DO_ACM_CHANNEL;
        break;
    case IET_PT100:
    case IET_IEPE:
        goto err_retrun;
    default:
        prt_erro("Invalid type %d for %s\n", type, __func__);
        return 0;
    }
    prt_info("%s get %d device\n", __func__, num);

    return num;

err_retrun:
    prt_erro("%s get no device\n", __func__);

    return 0;
};

/* 打开设备 */
int DOMOpen(int moduleType)
{
    struct DoCtrl_t *pctrl = &DoCtrl;
    struct DoDev_t *pdev = &pctrl->dev[0];
    int ret = 0;
    uint8_t i = 0;
    int8_t j = 0;

    pctrl->num = DOMGetDevNum(moduleType);
    pctrl->max_num = DO_MAX_CHANNEL;
    pctrl->type = (uint8_t)moduleType;
    /* 检查参数 */
    if (pctrl->num > pctrl->max_num) {
        prt_erro("Get DO number %d, max is %d\n", pctrl->num, pctrl->max_num);
        return -EFAULT;
    }
    /* 生成路径 */
    snprintf(pctrl->name, sizeof(pctrl->name), "%s", DO_DRV_NAME);
    for (i = 0; i < pctrl->num; i++) {
        pdev = &pctrl->dev[i];
        pdev->parent = pctrl;
        snprintf(pdev->name, sizeof(pdev->name), "%s%d", pctrl->name, i);
        pdev->index = i;
        pdev->fd = -ENODEV;
        ret = snprintf(pdev->path, PATH_MAX, "/dev/%s", pdev->name);
        if (ret < 0 || ret >= PATH_MAX) {
            prt_erro("Generate %s path failed\n", pdev->name);
            return -ENAMETOOLONG;
        }
        /* 打开设备 */
        pdev->fd = open(pdev->path, O_RDWR);
        if (pdev->fd < 0) {
            ret = errno;
            prt_erro("Open %s device failed, %s\n", pdev->path, strerror(ret));
            ret = -ret;
            goto err_close_fd;
        }
        prt_dbug("Open %s %d device success\n", pdev->path, pdev->fd);
    }

    pdev = &pctrl->dev[0];
    /* 获取驱动版本号 */
    ret = ioctl(pdev->fd, IOCTL_DO_GET_DRV_VERSION, &pctrl->drvVer);
    if (ret < 0) {
        prt_erro("Get DO driver version 0x%08x failed\n", pctrl->drvVer);
        goto err_close_fd;
    }
    prt_dbug("Get DO driver version 0x%08x\n", pctrl->drvVer);
    /* 获取 FPGA 版本号 */
    ret = ioctl(pdev->fd, IOCTL_DO_GET_FPGA_VERSION, &pctrl->fpgaVer);
    if (ret < 0) {
        prt_erro("Get DO FPGA version 0x%08x failed\n", pctrl->fpgaVer);
        goto err_close_fd;
    }
    prt_dbug("Get DO FPGA version 0x%08x\n", pctrl->fpgaVer);
    pctrl->libVer = LIB_VERSION(DO_VER_MAJOR, DO_VER_MINOR, DO_VER_PATCH);
    prt_dbug("Get DO library version 0x%08x\n", pctrl->libVer);

    return 0;

err_close_fd:
    for (j = i - 1; j >= 0; j--) {
        pdev = &pctrl->dev[j];
        close(pdev->fd);
        pdev->fd = -ENODEV;
    }

    return ret;
}

/* 初始化设备 */
int DOMInit(int channel, struct DomParam doParam)
{
    struct DoCtrl_t *pctrl = &DoCtrl;
    struct DoDev_t *pdev = &pctrl->dev[0];
    int ret = 0;
    uint8_t i = 0;
    uint8_t start = 0;
    uint8_t end = 0;
    uint8_t timeoutSta = 0;

    /* 检查参数 */
    if (channel < 0 || 
        doParam.timeOutEnable < 0 || doParam.timeOutEnable > DO_DEV_TIMEOUT_ON || 
        doParam.timeOut < 0 || doParam.timeOut > UINT8_MAX) {
        prt_erro("Invalid parameters for %s\n", __func__);
        return -EFAULT;
    }
    if (channel >= pctrl->num) {
        start = 0;
        end = pctrl->num;
    } else {
        start = channel;
        end = channel + 1;
    }
    /* 配置超时状态 */
    switch (doParam.timeOutEnable) {
    case DO_TIMEOUT_OFF:
        timeoutSta = DO_DEV_TIMEOUT_OFF;
        break;
    case DO_TIMEOUT_ON:
        timeoutSta = DO_DEV_TIMEOUT_ON;
        break;
    default:
        prt_erro("Invalid timeout status %d for %s\n", doParam.timeOutEnable, __func__);
        ret = -EFAULT;
        goto err_return;
    }

    for (i = start; i < end; i++) {
        pdev = &pctrl->dev[i];
        pdev->setMode = DO_DEV_MOD_LEVEL;
        pdev->timeoutSta = timeoutSta;
        pdev->timeout = doParam.timeOut;
        /* 检查参数 */
        if (pdev->fd == 0) {
            prt_erro("Invalid %s->fd for %s\n", pdev->name, __func__);
            ret = -EFAULT;
            goto err_return;
        }
        /* 配置 DO 模式 */
        ret = ioctl(pdev->fd, IOCTL_DO_SET_MODE, &pdev->setMode);
        if (ret < 0) {
            prt_erro("Set %s mode %d failed\n", pdev->name, pdev->setMode);
            goto err_return;
        }
        prt_dbug("Set %s mode %d success\n", pdev->name, pdev->setMode);
        /* 配置 DO 超时状态 */
        ret = ioctl(pdev->fd, IOCTL_DO_SET_TIMEOUT_STA, &pdev->timeoutSta);
        if (ret < 0) {
            prt_erro("Set %s timeout status %d failed\n", pdev->name, pdev->timeoutSta);
            goto err_return;
        }
        prt_dbug("Set %s timeout status %d success\n", pdev->name, pdev->timeoutSta);
        /* 配置 DO 超时时间 */
        ret = ioctl(pdev->fd, IOCTL_DO_SET_TIMEOUT, &pdev->timeout);
        if (ret < 0) {
            prt_erro("Set %s timeout %ds failed\n", pdev->name, pdev->timeout);
            goto err_return;
        }
        prt_dbug("Set %s timeout %ds success\n", pdev->name, pdev->timeout);
    }

    return 0;

err_return:

    return ret;
}

/* 写单个设备 */
int DOMWrite(int channel, unsigned char data)
{
    struct DoCtrl_t *pctrl = &DoCtrl;
    struct DoDev_t *pdev = &pctrl->dev[0];
    int ret = 0;

    /* 检查参数 */
    if (channel < 0 || channel >= pctrl->num) {
        prt_erro("Invalid parameters for %s\n", __func__);
        return -EFAULT;
    }
    pdev = &pctrl->dev[channel];
    /* 检查参数 */
    if (pdev->fd == 0) {
        prt_erro("Invalid %s->fd for %s\n", pdev->name, __func__);
        return -EFAULT;
    }
    if (pdev->setMode == DO_DEV_MOD_PULSE) {
        /* 停止 DO 脉冲 */
        ret = ioctl(pdev->fd, IOCTL_DO_STOP_PULSE);
        if (ret < 0) {
            prt_erro("Stop DO%d pulse failed\n", pdev->index);
            return -EFAULT;
        }
        prt_dbug("Stop DO%d pulse success\n", pdev->index);
    }
    pdev->setMode = DO_DEV_MOD_LEVEL;
    /* 配置 DO 模式 */
    ret = ioctl(pdev->fd, IOCTL_DO_SET_MODE, &pdev->setMode);
    if (ret < 0) {
        prt_erro("Set %s mode %d failed\n", pdev->name, pdev->setMode);
        return -EFAULT;
    }
    prt_dbug("Set %s mode %d success\n", pdev->name, pdev->setMode);

    if (data == 0) {
        pdev->setLevel = data;
    } else {
        pdev->setLevel = 1;
    }
    /* 配置 DO 电平 */
    ret = ioctl(pdev->fd, IOCTL_DO_SET_LEVEL, &pdev->setLevel);
    if (ret < 0) {
        prt_erro("Set DO%d level failed\n", pdev->index);
        return -EFAULT;
    }
    prt_info("Set DO%d value %d\n", pdev->index, pdev->setLevel);

    return 0;
}

/* 写全部设备 */
int DOMWriteAll(unsigned int data)
{
    struct DoCtrl_t *pctrl = &DoCtrl;
    struct DoDev_t *pdev = &pctrl->dev[0];
    int ret = 0;
    uint8_t i = 0;

    /* 检查参数 */
    if (pdev->fd == 0) {
        prt_erro("Invalid %s->fd for %s\n", pdev->name, __func__);
        return -EFAULT;
    }
    for (i = 0; i < pctrl->num; i++) {
        pdev = &pctrl->dev[i];
        if (pdev->setMode == DO_DEV_MOD_PULSE) {
            /* 停止 DO 脉冲 */
            ret = ioctl(pdev->fd, IOCTL_DO_STOP_PULSE);
            if (ret < 0) {
                prt_erro("Stop DO%d pulse failed\n", pdev->index);
                return -EFAULT;
            }
            prt_dbug("Stop DO%d pulse success\n", pdev->index);
        }
        pdev->setMode = DO_DEV_MOD_LEVEL;
        /* 配置 DO 模式 */
        ret = ioctl(pdev->fd, IOCTL_DO_SET_MODE, &pdev->setMode);
        if (ret < 0) {
            prt_erro("Set %s mode %d failed\n", pdev->name, pdev->setMode);
            return -EFAULT;
        }
        prt_dbug("Set %s mode %d success\n", pdev->name, pdev->setMode);
    }
    pctrl->setAllLevel = data;
    /* 配置全部 DO 电平 */
    ret = ioctl(pdev->fd, IOCTL_DO_SET_ALL_LEVEL, &pctrl->setAllLevel);
    if (ret < 0) {
        prt_erro("Set all DO level failed\n");
        return -EFAULT;
    }
    prt_info("Set all DO value 0x%08x success\n", pctrl->setAllLevel);

    return 0;
}

int DOMPulseWrite(int channel, struct PulseParam pulseParam)
{
    struct DoCtrl_t *pctrl = &DoCtrl;
    struct DoDev_t *pdev = &pctrl->dev[0];
    int ret = 0;
    uint8_t i = 0;
    uint8_t start = 0;
    uint8_t end = 0;

    /* 检查参数 */
    if (channel < 0 || 
        pulseParam.number < 0 || 
        pulseParam.pulsewidth < 0 || pulseParam.pulsewidth >= pulseParam.pulseInterval || 
        pulseParam.pulseInterval < 0 || pulseParam.pulseInterval > UINT16_MAX) {
        prt_erro("Invalid parameters for %s\n", __func__);
        return -EFAULT;
    }
    if (channel >= pctrl->num) {
        start = 0;
        end = pctrl->num;
    } else {
        start = channel;
        end = channel + 1;
    }
    for (i = start; i < end; i++) {
        pdev = &pctrl->dev[i];
        pdev->setMode = DO_DEV_MOD_PULSE;
        pdev->setPulse.high = pulseParam.pulsewidth;
        pdev->setPulse.cycle = pulseParam.pulseInterval;
        pdev->setPulse.num = pulseParam.number;
        /* 检查参数 */
        if (pdev->fd == 0) {
            prt_erro("Invalid %s->fd for %s\n", pdev->name, __func__);
            ret = -EFAULT;
            goto err_return;
        }
        /* 配置 DO 模式 */
        ret = ioctl(pdev->fd, IOCTL_DO_SET_MODE, &pdev->setMode);
        if (ret < 0) {
            prt_erro("Set %s mode %d failed\n", pdev->name, pdev->setMode);
            return -EFAULT;
        }
        prt_dbug("Set %s mode %d success\n", pdev->name, pdev->setMode);
        /* 配置 DO 脉冲参数 */
        ret = ioctl(pdev->fd, IOCTL_DO_SET_PULSE, &pdev->setPulse);
        if (ret < 0) {
            prt_erro("Set DO%d pulse high %d cycle %d num %d failed\n", pdev->index, 
                     pdev->setPulse.high, pdev->setPulse.cycle, pdev->setPulse.num);
            goto err_return;
        }
        prt_dbug("Set DO%d pulse high %d cycle %d num %d success\n", pdev->index, 
                 pdev->setPulse.high, pdev->setPulse.cycle, pdev->setPulse.num);
        /* 开始 DO 脉冲 */
        ret = ioctl(pdev->fd, IOCTL_DO_START_PULSE);
        if (ret < 0) {
            prt_erro("Start DO%d pulse failed\n", pdev->index);
            return -EFAULT;
        }
        prt_dbug("Start DO%d pulse success\n", pdev->index);
    }

    return 0;

err_return:

    return ret;
}

int DOMRead(int channel, unsigned int *data)
{
    struct DoCtrl_t *pctrl = &DoCtrl;
    struct DoDev_t *pdev = &pctrl->dev[0];
    int ret = 0;
    uint8_t i = 0;
    uint8_t start = 0;
    uint8_t end = 0;

    /* 检查参数 */
    if (channel < 0 || !data) {
        prt_erro("Invalid parameters for %s\n", __func__);
        return -EFAULT;
    }
    if (channel >= pctrl->num) {
        start = 0;
        end = pctrl->num;
    } else {
        start = channel;
        end = channel + 1;
    }
    for (i = start; i < end; i++) {
        pdev = &pctrl->dev[i];
        /* 检查参数 */
        if (pdev->fd == 0) {
            prt_erro("Invalid %s->fd for %s\n", pdev->name, __func__);
            ret = -EFAULT;
            goto err_return;
        }
        /* 获取 DO 电平 */
        ret = ioctl(pdev->fd, IOCTL_DO_GET_LEVEL, &pdev->getLevel);
        if (ret < 0) {
            prt_erro("Get DO%d level failed\n", pdev->index);
            goto err_return;
        }
        prt_info("Get DO%d level %d success\n", pdev->index, pdev->getLevel);
        data[i - start] = pdev->getLevel;
    }

    return 0;

err_return:

    return ret;
}

int DOMClose()
{
    struct DoCtrl_t *pctrl = &DoCtrl;
    struct DoDev_t *pdev = &pctrl->dev[0];
    int ret = 0;
    uint8_t i = 0;

    for (i = 0; i < pctrl->num; i++) {
        pdev = &pctrl->dev[i];
        /* 检查参数 */
        if (pdev->fd == 0) {
            prt_erro("Invalid %s->fd for %s\n", pdev->name, __func__);
            return -EFAULT;
        }
        if (pdev->setMode == DO_DEV_MOD_PULSE) {
            /* 停止 DO 脉冲 */
            ret = ioctl(pdev->fd, IOCTL_DO_STOP_PULSE);
            if (ret < 0) {
                prt_erro("Stop DO%d pulse failed\n", pdev->index);
                return -EFAULT;
            }
            prt_dbug("Stop DO%d pulse success\n", pdev->index);
        }
        /* 关闭设备 */
        ret = close(pdev->fd);
        if (ret != 0) {
            prt_erro("Close %s failed\n", pdev->path);
            return -EFAULT;
        }
        pdev->fd = 0;
        prt_dbug("Close %s success\n", pdev->path);
    }

    return 0;
}

/* 获取驱动版本号 */
int DOMGetDrvVer()
{
    struct DoCtrl_t *pctrl = &DoCtrl;

    prt_dbug("%s %d.%d.%d\n", __func__, ((pctrl->drvVer >> 24) & 0xFF), 
             ((pctrl->drvVer >> 16) & 0xFF), (pctrl->drvVer & 0xFFFF));

    return (int)(pctrl->drvVer >> 16);
}

/* 获取 FPGA 版本号 */
int DOMGetFpgaVer()
{
    struct DoCtrl_t *pctrl = &DoCtrl;

    prt_dbug("%s %d.%d\n", __func__, ((pctrl->fpgaVer >> 8) & 0xFF), 
             (pctrl->fpgaVer & 0xFF));

    return (int)(pctrl->fpgaVer);
}

/* 获取动态库版本号 */
int DOMGetLibVer()
{
    struct DoCtrl_t *pctrl = &DoCtrl;

    prt_dbug("%s %d.%d.%d\n", __func__, ((pctrl->libVer >> 24) & 0xFF), 
             ((pctrl->libVer >> 16) & 0xFF), (pctrl->libVer & 0xFFFF));

    return (int)(pctrl->libVer >> 16);
}
